/*
 * Created by SharpDevelop.
 * User: Edward Wilde (http://blogs.charteris.com/edwardw)
 * Date: 01/07/2007
 * Time: 18:40
 * 
 */

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;

namespace Wax
{
    /// <summary>
    /// The test class is responsible for executing all of the <see cref="Test.Commands" /> that comprise of this test.
    /// </summary>
    public class Test
    {

        #region Private fields

        /// <summary>
        /// A list of all the commands that comprise this <see cref="Test" />.
        /// </summary>
        private readonly Collection<Command> commands;

        /// <summary>
        /// The test id.
        /// </summary>
        private string id;

        /// <summary>
        /// The number of successfully completed commands
        /// </summary>
        private int numberOfCompletedCommands;

        /// <summary>
        /// The number of commands that failed to complete successfully.
        /// </summary>
        private int numberOfFailedCommands;

        /// <summary>
        /// Collection of errors encountered during the execution of the <see cref="Commands"/>. Note
        /// only used if <see cref="ThrowErrors"/> is set to <c>false</c>.
        /// </summary>
        private readonly Collection<CommandException> errors;

        /// <summary>
        /// If <c>true</c> will throw any errors encountered during the execution of the <see cref="Commands"/>. If <c>false</c> will
        /// traps any errors encountered during the execution of the <see cref="Commands"/> and make those exceptions available via
        /// <see cref="Errors"/>. The default value is set to <c>true</c>, errors will be thrown.
        /// </summary>
        private bool throwErrors;

        #endregion

        #region Constructors

        /// <summary>
        /// Creates a new instance of the <see cref="Test" /> class.
        /// </summary>
        public Test()
        {
            this.commands = new Collection<Command>();
            this.errors = new Collection<CommandException>();
        }

        #endregion

        #region Public events

        /// <summary>
        /// Occurs when a <see cref="Commands">command</see> throws an error.
        /// </summary>
        public event EventHandler<TestErrorEventArgs> ErrorOccurred;

        /// <summary>
        /// Occurs when a command has been executed, successfully or otherwise.
        /// </summary>
        public event EventHandler<CommandCompletedEventArgs> CommandCompleted;

        #endregion

        #region Public properties

        /// <summary>
        /// A list of all the commands that comprise this <see cref="Test" />.
        /// </summary>
        public Collection<Command> Commands
        {
            get { return this.commands; }
        }

        /// <summary>
        /// If <c>true</c> will throw any errors encountered during the execution of the <see cref="Commands"/>. If <c>false</c> will
        /// traps any errors encountered during the execution of the <see cref="Commands"/> and make those exceptions available via
        /// <see cref="Errors"/>. The default value is set to <c>true</c>, errors will be thrown.
        /// </summary>
        public bool ThrowErrors
        {
            get { return throwErrors; }
            set { throwErrors = value; }
        }

        /// <summary>
        /// Collection of errors encountered during the execution of the <see cref="Commands"/>. Note
        /// only used if <see cref="ThrowErrors"/> is set to <c>false</c>.
        /// </summary>	    
        public Collection<CommandException> Errors
        {
            get { return errors; }
        }

        /// <summary>
        /// Gets or sets the test id.
        /// </summary>
        /// <value>The test id.</value>
        public string Id
        {
            get { return this.id; }
            set { this.id = value; }
        }

        /// <summary>
        /// The number of successfully completed commands
        /// </summary>
        public int NumberOfCompletedCommands
        {
            get { return numberOfCompletedCommands; }
        }

        /// <summary>
        /// The number of commands that failed to complete successfully.
        /// </summary>
        public int NumberOfFailedCommands
        {
            get { return numberOfFailedCommands; }
        }

        #endregion

        #region Public methods

        /// <summary>
        /// Returns a <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
        /// </returns>
        public override string ToString()
        {
            return string.Format(CultureInfo.InvariantCulture, "Id: {1}{0}Number of commands:{2}", System.Environment.NewLine, this.Id, this.Commands.Count);
        }

        /// <summary>
        /// Executes this test
        /// </summary>
        /// <param name="context">The execution context used to execute this test</param>
        [CLSCompliant(false)]
        public void Execute(IExecutionContext context)
        {
            this.Execute(context, true);
        }

        /// <summary>
        /// Executes this test
        /// </summary>
        /// <param name="context">The execution context used to execute this test</param>
        /// <param name="shouldThrowErrors">if set to <c>true</c> throws any errors errors encountered during the execution of the <see cref="Commands"/>.</param>
        [CLSCompliant(false)]
        public void Execute(IExecutionContext context, bool shouldThrowErrors)
        {
            this.ThrowErrors = shouldThrowErrors;
            this.numberOfCompletedCommands = 0;
            this.numberOfFailedCommands = 0;

            foreach (Command command in this.Commands)
            {
                bool success = false;
                System.Diagnostics.Debug.Assert(command != null, "Command object should not be null");

                try
                {
                    command.Execute(context);
                    success = true;
                    this.numberOfCompletedCommands = this.numberOfCompletedCommands + 1;
                }
                catch (FindMethodException findException)
                {
                    if (OnErrorOccured(new ExecutionException(findException.Message, findException, command)))
                    {
                        throw;
                    }
                }
                catch (CommandException commandException)
                {
                    if (OnErrorOccured(commandException))
                    {
                        throw;
                    }
                }
                catch (ValidateException validateException)
                {
                    if (OnErrorOccured(new ExecutionException(validateException.Message, validateException, command)))
                    {
                        throw;
                    }
                }
                catch (Exception exception)
                {
                    if (OnErrorOccured(new ExecutionException(exception.Message, exception, command)))
                    {
                        throw;
                    }
                }

                if (this.CommandCompleted != null)
                {
                    this.CommandCompleted(command, new CommandCompletedEventArgs(this.numberOfCompletedCommands, this.numberOfFailedCommands, success));
                }
            }            
        }

        /// <summary>
        /// Raises the test error event
        /// </summary>
        /// <param name="commandException"></param>
        /// <returns><c>true</c> if the error should be re-thrown;otherwise <c>false</c></returns>
        private bool OnErrorOccured(CommandException commandException)
        {
            this.numberOfFailedCommands = this.numberOfFailedCommands + 1;

            if (!this.ThrowErrors)
            {
                this.Errors.Add(commandException);

                if (this.ErrorOccurred != null)
                {
                    this.ErrorOccurred(this, new TestErrorEventArgs(new TestError(commandException, this)));
                }

                return false;
            }
            
            return true;
        }

        #endregion
    }
}
